//The Ray class

#include "Ray.h"
#include "Color.h"
#include "Scene.h"



/*Ray
*	The generic Ray constructor.
*Parameters:
*	none
*/
Ray::Ray() {
	Color cLight(1, 1, 1);
}

//The initialized Ray constructor
Ray::Ray(Vector3 p, Vector3 d, Scene s) {
	Ray();
	origin = p;
	direction = d;
	scene = s;
	cBackground = Color(0, 0, 0, 1);

	/*Vector3 inv_direction = Vector3(1/direction.x, 1/direction.y, 1/direction.z);
	
	sign[0] = (inv_direction.x < 0);
	sign[1] = (inv_direction.y < 0);
	sign[2] = (inv_direction.z < 0);*/
}

/*~Ray
*	The Ray destructor.
*/
Ray::~Ray() {}

Color Ray::trace() {
	return trace(origin, direction, 0);
}

//Given the origin and the direction, as well as the current depth,
//traces the ray recursively
Color Ray::trace(Vector3 p, Vector3 d, int depth) {
	
	Color cLocal, cTransmitted, cReflected;
	Vector3 n, r, t;
	Vector3 q;
	Intersection iHit;
	Status status;
	
	//if we've passed the maximum depth
	if (depth > MAX_DEPTH) {
		return cBackground;
	}
	
	//find the intersection information
	iHit = intersect(p, d, false);
	
	//if we've hit a light
	if (iHit.type == LIGHT) {
		return iHit.object->color;
	}
	
	//if no object was hit
	else if (iHit.type == NONE) {
		return cBackground;
	}
	else { //insersected with an object
		q = iHit.intersectionPoint;
		n = normal(q, iHit.object);
		r = reflect(d, n);
		t = transmit(q, n);
		
		cLocal = phong(p, q, n, r, iHit.object);
		cReflected = trace(q+(r*0.005), r, depth+1);
		
		//cTransmitted = trace(q, t, depth+1); //not implemented
		
		//add all colours and bound the sum
		Color c = cLocal + cReflected + cTransmitted;
		c.boundColor();
		return c;
	}
}

//Finds the object of intersection
Intersection Ray::intersect(Vector3 p, Vector3 d, bool lightCheck) {
	int index = -1;
	closestT = 1000000;
	Intersection intersection;
	Object* obj;
	Object* tmpObj;
	//Loop over all objects in the scene, and look for the closest one
	for (int i = 0; i < scene.objects.size(); i++) {
		float distance;
		tmpObj = scene.objects[i]->tIntersect(*this, distance);

		if (distance != -1 && distance < closestT) {
			closestT = distance;
			obj = tmpObj;
			index = i;
		}
		
		/*if (scene.objects[i]->positiveDiscriminant) {
			if (scene.objects[i]->t1 > 0 && scene.objects[i]->t2 > 0) {
				if (scene.objects[i]->t1 < closestT || scene.objects[i]->t2 < closestT) {
					//closestT = Color::min(scene.objects[i]->t1, scene.objects[i]->t2);
					closestT = g_min(scene.objects[i]->t1, scene.objects[i]->t2);
					index = i;
				}
			}
			else if (scene.objects[i]->t1 > 0) {
				if (scene.objects[i]->t1 < closestT) {
					closestT = scene.objects[i]->t1;
					index = i;
				}
			}
			else if (scene.objects[i]->t2 > 0) {
				if (scene.objects[i]->t2 < closestT) {
					closestT = scene.objects[i]->t2;
					index = i;
				}
			}
		}*/
	}
	
	float closestLightIndex = 0;
	float tTest;
	
	//Checks to see if a light is intersected
	for (int i = 0; i < scene.lights.size(); i++) {
		scene.lights[i].tIntersect(p, d);
		tTest = scene.lights[i].t;
		
		if (tTest < scene.lights[closestLightIndex].t && tTest > 0) {
			closestLightIndex = i;
		}
	}
	
	//Create the intersection object
	//if (index != -1) {
	if (closestT != 1000000) {
		intersection.type = OBJECT;
		intersection.intersectionPoint = p + (d*closestT);
		intersection.object = obj;//scene.objects[index];
	}
	else {
		intersection.type = NONE;
	}
	
	if (lightCheck) {
		if (index == -1) {
			intersection.type = LIGHT;
		}
		
		else {
			if (closestT > scene.lights[closestLightIndex].t) {
				intersection.type = LIGHT;
			}
		}
	}

	//DebugWriter debug;
	//debug.writeToFile(closestT);
	
	return intersection;
}

//Return the normal of an object
Vector3 Ray::normal(Vector3 p, Object *object) {
	return object->normal(p);
}

//Calculate the direction of the reflected ray
Vector3 Ray::reflect(Vector3 d, Vector3 n) {
	n.normalize();
	d.normalize();
	
	Vector3 reflected = d + (n * Vector3::dotProduct(n, d) * (-1) * 2);
	reflected.normalize();
	
	return reflected;
}

//TODO
Vector3 Ray::transmit(Vector3 q, Vector3 n) {
	return Vector3();
}

//The Phong lighting method
Color Ray::phong(Vector3 p, Vector3 q, Vector3 n, Vector3 r, Object *object) {
	Color ret(0,0,0);
	
	ret = object->material.ambient * scene.globalAmbient;
	ret = ret + object->getExtraColor(q);
	
	//for each light in the light array
	for (int i = 0; i < scene.lights.size(); i++) {
		//Check if the object is in shadow
		Vector3 shadowRay(scene.lights[i].position - q);
		shadowRay.normalize();
		
		Intersection hit = intersect(q+(shadowRay*0.005), shadowRay, true);
		
		//If we did not hit anything or if what we hit is beyond the light then
		if (hit.type == LIGHT) {
			//Calc the diffuse, clamp the values of the dot product
			//because we don't want to subtract light here
			//if the normal is not facing the light
			
			ret = ret + object->material.diffuse * scene.lights[i].diffuse * g_max(Vector3::dotProduct(n, shadowRay), 0);
			
			//Specular
			
			Vector3 R = n * Vector3::dotProduct(n, shadowRay) * 2 - shadowRay;
			Vector3 V = p - q;
			
			R.normalize();
			V.normalize();
			
			ret = ret + object->material.specular * scene.lights[i].specular * pow(g_min(g_max(Vector3::dotProduct(R, V), 0), 1), object->material.shininess);
		}
	}
	
	// Important : Clamp the color here between 0-1 before returning!
	ret.boundColor();
	
	return ret;
}